<template>
    <div>
        <h2>
            linprog
            <el-button type="primary" @click="toLoadData">加载数据</el-button>
        </h2>
<!--        formData:<pre>{{formData}}</pre>-->
<!--        <hr/>-->
<!--        aArr:{{aArr}}-->
<!--        <hr/>-->
<!--        bArr:{{bArr}}-->
<!--        <hr/>-->
<!--        aeqArr:{{aeqArr}}-->
<!--        <hr/>-->
<!--        beqArr:{{beqArr}}-->

        <el-form label-width="110px" >
            <el-form-item label="测试用例名称：">
                <el-input v-model="formData.caseName"></el-input>
            </el-form-item>
            <el-form-item label="变量个数：">
                <el-input-number v-model="formData.varNum" :min="2" :max="100" :step="1" @change="genBoundTableData"></el-input-number>
            </el-form-item>
            <el-form-item label="目标函数：">
                <template v-for="varIndex in formData.varNum" >
                    <span v-show="varIndex > 1" :key="varIndex">+</span>
                    <el-input :key="varIndex + '_target_input'" style="width:100px;margin-left:5px;" v-model="formData.fArr[varIndex - 1]"></el-input>
                    <span :key="varIndex + '_target_span'" style="margin-left:5px;">x{{varIndex}}</span>
                </template>
                <span style="margin-left: 5px;">-></span>
                <el-select v-model="formData.target" placeholder="请选择" style="width: 100px;">
                    <el-option
                            v-for="item in targetOptions"
                            :key="item.value"
                            :label="item.label"
                            :value="item.value">
                    </el-option>
                </el-select>
            </el-form-item>
            <el-form-item label="式子：">
                <div v-for="(varA, varAIndex) in formData.aArr" style="padding-top:5px;margin-top:5px;" :key="varAIndex">
                    <template v-for="varIndex in formData.varNum">
                        <span :key="varIndex + '_a_arr_span'" v-show="varIndex > 1" >+</span>
                        <el-input :key="varIndex + '_a_arr_input'" style="width:100px;margin-left:5px;" v-model="varA[varIndex - 1]"></el-input>
                        <span :key="varIndex + '_a_arr_span2'" style="margin-left:5px;">x{{varIndex}}</span>
                    </template>
                    <el-select v-model="formData.operatorArr[varAIndex]" placeholder="请选择" style="width: 70px;">
                        <el-option
                                v-for="item in operatorOptions"
                                :key="item.value"
                                :label="item.label"
                                :value="item.value">
                        </el-option>
                    </el-select>
                    <el-input style="width:100px;margin-left:10px;" v-model="formData.bArr[varAIndex]"></el-input>
                    <el-button type="primary" @click="delA(varAIndex)">删除</el-button>
                    <hr style="margin-top:5px;"/>
                </div>
                <div style="margin-top:5px;">
                    <el-button type="primary" @click="addA">添加式子</el-button>
                </div>
            </el-form-item>
<!--            <el-form-item label="等于：">-->
<!--                <div v-for="(varA, varAIndex) in formData.aeqArr" style="padding-top:5px;">-->
<!--                    <template v-for="varIndex in formData.varNum">-->
<!--                        <span v-show="varIndex > 1">+</span>-->
<!--                        <el-input style="width:50px;margin-left:5px;" v-model="varA[varIndex - 1]"></el-input><span style="margin-left:5px;">x{{varIndex}}</span>-->
<!--                    </template>-->
<!--                    = <el-input style="width:50px;margin-left:10px;" v-model="formData.beqArr[varAIndex]"></el-input>-->
<!--                    <el-button type="primary" @click="delAeq(varAIndex)">删除</el-button>-->
<!--                </div>-->
<!--                <div style="margin-top:5px;">-->
<!--                    <el-button type="primary" @click="addAeq">添加式子</el-button>-->
<!--                </div>-->
<!--            </el-form-item>-->
            <el-form-item label="变量范围：">
                <el-table
                        :data="formData.boundTableData"
                        style="width: 100%">
                    <el-table-column
                            prop="var"
                            label="变量"
                            width="180">
                    </el-table-column>
                    <el-table-column
                            prop="min"
                            label="下限" width="250">
                        <template slot-scope="scope">
                            <el-input v-model="scope.row.min" style="width:200px;" :disabled="scope.row.minmin">
                                <template slot="append">
                                    <el-checkbox v-model="scope.row.minmin" @change="setMinMin(scope.row)">无穷小</el-checkbox>
                                </template>
                            </el-input>
                        </template>
                    </el-table-column>
                    <el-table-column
                            prop="max"
                            label="上限" width="250">
                        <template slot-scope="scope">
                            <el-input v-model="scope.row.max" style="width:200px;" :disabled="scope.row.maxmax">
                                <template slot="append">
                                    <el-checkbox v-model="scope.row.maxmax" @change="setMaxMax(scope.row)">无穷大</el-checkbox>
                                </template>
                            </el-input>
                        </template>
                    </el-table-column>
                </el-table>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="toCalc">计算</el-button>
                <el-button type="primary" @click="generateMatLabCode1('linprog')">生成MATLAB代码</el-button>
                <el-button type="primary" @click="generateMatLabCode1('intlinprog')">生成MATLAB（整数）代码</el-button>
                <el-button type="primary" @click="toDownload">下载测试数据</el-button>
            </el-form-item>
            <el-form-item label="结果：" v-show="showResult">
                <div v-loading="loading">
                    <p>x值：{{solution.x}}</p>
                    <p>目标函数值：{{solution.fun}}</p>
                    <p style="margin-top:5px;">分枝定界树形数据：</p>
                    <el-tree :data="boundDataTree" :props="defaultProps"  default-expand-all empty-text="暂无分支定界步骤数据">
                        <span slot-scope="{ node, data }">
                            <span style="margin-left:2px;">
                                <span v-if="data.result.success" :class="{'champion_cls' : isChampion(data) }">
                                    {{formatVal(data.result.fun)}}
                                </span>
                                <span v-else>无解</span>
                            </span>
                            <span style="margin-left:2px;">x: {{data.result.x}}</span>
                            <span>范围：{{data.bound}}</span>
                        </span>

                    </el-tree>
                    <p style="margin-top:5px;">分枝定界表格数据：</p>
                    <el-table :data="flatBoundDataTree" >
                        <el-table-column
                                type="index"
                                width="50">
                        </el-table-column>
                        <el-table-column prop="result.success" :label="formData.target == 'min' ? '最小值' : '最大值'" width="120px">
                            <template slot-scope="scope">
                                <span :class="{'champion_cls' : isChampion(scope.row)}">{{scope.row.result.success ? formatNumber(scope.row.result.fun) : '无解'}}</span>
                            </template>
                        </el-table-column>
                        <el-table-column label="范围" min-width="100">
                            <template slot-scope="scope">
                                <span>{{scope.row.bound}}</span>
                            </template>
                        </el-table-column>
                        <el-table-column label="x值" min-width="100">
                            <template slot-scope="scope">
                                <span>{{scope.row.result.x}}</span>
                            </template>
                        </el-table-column>
                    </el-table>
                    <p style="margin-top:5px;">分枝定界目标函数值（整数规划）：{{haveChampion ? formatNumber(solution.fun_int) : '无解'}}</p>
                </div>

            </el-form-item>
        </el-form>

        <el-dialog
                title="数据"
                :visible.sync="dialogVisible"
                width="30%">
            <span>
                <el-input
                        type="textarea"
                        :autosize="{ minRows: 3, maxRows: 25}"
                        placeholder="请粘贴内容"
                        v-model="sourceFormDataStr">
                </el-input>
            </span>
            <span slot="footer" class="dialog-footer">
            <el-button @click="dialogVisible = false">取 消</el-button>
            <el-button type="primary" @click="toApplySourceData">确 定</el-button>
          </span>
        </el-dialog>
    </div>
</template>

<script>
    import FileSaver from 'file-saver'
    import lodash from 'lodash'
    import axios from 'axios'
    export default {
        name: "linprog1",
        data() {
            return {
                sourceFormDataStr: null,
                dialogVisible: false,
                loading: false,
                showResult: false,
                targetOptions: [{
                    value: 'min',
                    label: '最小值'
                }, {
                    value: 'max',
                    label: '最大值'
                }],
                operatorOptions: [{
                    value: 'lessThan',
                    label: '≤'
                }, {
                    value: 'greatThan',
                    label: '≥'
                }, {
                    value: 'equal',
                    label: '='
                }],
                formData: {
                    caseName: '测试',
                    varNum: 2,
                    target: 'max',
                    fArr: [40, 90],
                    aArr: [
                        [9, 7],
                        [7, 20],
                    ],
                    operatorArr: ['lessThan', 'lessThan'],
                    bArr: [56, 70],
                    auneqArr: [],
                    buneqArr: [],
                    aeqArr: [],
                    beqArr: [],
                    boundTableData: []
                },
                solution: {

                },
                boundDataTree: [],
                flatBoundDataTree: [],
                defaultProps: {
                    children: 'children',
                    label: 'label'
                }
            }
        },
        methods: {
            isChampion(row) {
                return this.isAllInteger(row.result.x) && Math.abs(row.result.fun - this.solution.fun_int) <= 0.01
            },
            toApplySourceData() {
                let sourceFormData = null
                try {
                    sourceFormData = JSON.parse(this.sourceFormDataStr)
                    // console.log('sourceFormData:', sourceFormData)
                    this.formData.caseName = sourceFormData.caseName
                    this.formData.varNum = sourceFormData.varNum
                    this.formData.target = sourceFormData.target
                    this.formData.fArr = sourceFormData.fArr
                    this.formData.aArr = sourceFormData.aArr
                    this.formData.operatorArr = sourceFormData.operatorArr
                    this.formData.bArr = sourceFormData.bArr
                    this.formData.boundTableData = sourceFormData.boundTableData

                    this.dialogVisible = false
                } catch (e) {
                    sourceFormData = null
                    this.$message.error('不是有效数据')
                }
            },
            toLoadData() {
                this.sourceFormDataStr = ''
                this.dialogVisible = true
                this.showResult = false
            },
            isAllInteger(arr) {
              let result = true
              if(!arr || arr.length == 0) {
                  result = false
              } else {
                  for(let a of arr) {
                      if(!Number.isInteger(a)) {
                          result = false
                          break
                      }
                  }
              }
              return result
            },
            formatNumber(val) {
              return val =  val ? Math.round(val * 100) / 100 : null
            },
            formatVal(val) {
              val =  val ? Math.round(val * 100) / 100 : '-'
              let targetDesc = this.formData.target == 'min' ? '最小值' : '最大值'
              return targetDesc + '：' + val
            },
            save() {
                let formData = lodash.cloneDeep(this.formData)
                this.$localStorage.set('linprog1FormData', JSON.stringify(formData), 24 * 60 * 60 * 1000) // keep 1 day
            },
            toDownload() {
                let blob = new Blob([JSON.stringify(this.formData,null,2)], {type: "text/plain;charset=utf-8"});
                let caseName = this.formData.caseName || '测试用例'
                FileSaver.saveAs(blob, caseName + ".txt");
            },
            generateMatLabCode1(func) {
                let postData = this.getPostData()
                let result = "A = ["
                postData.auneqArr.forEach(row => {
                    row.forEach(elm => {
                        result += parseFloat(elm) + " "
                    })
                    result = result.trim()
                    result += "\n"
                })
                result += '];'

                result += "\n"
                result += 'b = ['
                postData.buneqArr.forEach(elm => {
                    result += parseFloat(elm) + " "
                })
                result = result.trim()
                result += '];'

                result += "\n"
                result += "\n"
                result += "Aeq = ["
                postData.aeqArr.forEach(row => {
                    row.forEach(elm => {
                        result += parseFloat(elm) + " "
                    })
                    result = result.trim()
                    result += "\n"
                })
                result += '];'

                result += "\n"
                result += 'beq = ['
                postData.beqArr.forEach(elm => {
                    result += parseFloat(elm) + " "
                })
                result = result.trim()
                result += '];'

                result += "\n"
                result += "\n"
                let lbResult = 'lb = ['
                let ubResult = 'ub = ['
                postData.boundTableData.forEach(elm => {
                    lbResult += parseFloat(elm.min) + " "
                    ubResult += parseFloat(elm.max) + " "
                })
                lbResult = lbResult.trim()
                ubResult = ubResult.trim()
                lbResult += '];'
                ubResult += '];'

                result += lbResult
                result += "\n"
                result += ubResult
                result += "\n"

                result += "\n"
                result += 'f = ['
                postData.fArr.forEach(elm => {
                    result += parseFloat(elm) + " "
                })
                result = result.trim()
                result += '];'

                result += "\n"
                result += "\n"
                if(func == 'linprog') {
                    result += '[x, fval] = ' + func + '(f,A,b,Aeq,beq,lb,ub)'
                } else {
                    result += 'intcon = 1:' + postData.varNum + ';'
                    result += "\n"
                    result += '[x, fval] = ' + func + '(f, intcon, A, b, Aeq, beq, lb, ub)'
                }
                if(postData.target == 'max') {
                    result += '\nfval = -fval'
                }
                result += "\n"

                // console.log('result:', result)
                let blob = new Blob([result], {type: "text/plain;charset=utf-8"});
                let caseName = this.formData.caseName || '测试用例'
                if(func == 'intlinprog') {
                    caseName += '-整数规划'
                }
                FileSaver.saveAs(blob, caseName + ".m");
            },
            getPostData() {
                let postData = lodash.cloneDeep(this.formData)

                postData.aeqArr = []
                postData.beqArr = []
                postData.auneqArr = []
                postData.buneqArr = []

                postData.fArr.forEach((fv, index) => {
                    if(!fv) {
                        postData.fArr[index] = 1
                    }

                    if(this.formData.target != 'min') {
                        postData.fArr[index] *= -1
                    }
                })

                postData.aArr.forEach((aRow, index) => {
                    let operator = postData.operatorArr[index]
                    if(operator == 'equal') {
                        postData.aeqArr.push(aRow)
                        postData.beqArr.push(postData.bArr[index])
                    } else if(operator == 'lessThan'){
                        postData.auneqArr.push(aRow)
                        postData.buneqArr.push(postData.bArr[index])
                    } else {
                        aRow = aRow.map(x => -1 * x)
                        postData.auneqArr.push(aRow)
                        postData.buneqArr.push(-1 * postData.bArr[index])
                    }
                })

                delete postData.aArr
                delete postData.bArr
                delete postData.operatorArr
                // console.log('postData:', postData)
                return postData
            },
            async toCalc() {
                let boundDataTree = []
                this.save()
                this.loading = true
                this.showResult = true
                let postData = this.getPostData()

                try {
                    let response = await axios.post( '/linprog', postData)
                    let responseData = response.data
                    // console.log('responseData:', responseData)
                    if(responseData.success) {
                        this.solution = responseData
                    } else {
                        this.solution = {
                            x: '无解',
                            fun: '无解'
                        }
                    }
                    boundDataTree = responseData.bound_data_tree
                    this.$set(this.solution, 'fun_int', responseData.fun_int)
                } catch (e) {
                    this.solution = {
                        x: '无法计算',
                        fun: '无法计算'
                    }
                }

                this.boundDataTree = boundDataTree
                this.flatBoundDataTree = this.getFlatTreeData(boundDataTree)

                this.loading = false

            },
            getFlatTreeData(boundDataTree) {
                let resultList = []
                if(boundDataTree && boundDataTree.length > 0) {
                    boundDataTree.forEach(boundData => {
                        resultList.push(...this.parseTreeData(boundData))
                    })
                }
                return resultList
            },
            parseTreeData(nodeData) {
                // console.log('nodeData:', nodeData)
                let flatTreeData = []
                flatTreeData.push({
                    result: nodeData.result,
                    bound: nodeData.bound,
                })

                if(nodeData.children && nodeData.children.length > 0) {
                    nodeData.children.forEach(child => {
                        flatTreeData.push(...this.parseTreeData(child))
                    })
                }
                return flatTreeData
            },
            setMinMin(row) {
                this.$set(row, 'min', -99999999)
            },
            setMaxMax(row) {
                this.$set(row, 'max', 99999999)
            },
            addA() {
                this.formData.aArr.push([])
                this.formData.operatorArr.push('lessThan')
                this.formData.bArr.push(1)
                this.adjustAArr()
                this.adjustBArr()
            },
            delA(index) {
                this.formData.aArr.splice(index, 1)
                this.formData.operatorArr.splice(index, 1)
                this.formData.bArr.splice(index, 1)
            },
            genBoundTableData() {
                let boundTableData = []
                let fArr = []
                for(let i = 0; i < this.formData.varNum; i++) {
                    let f = i <= this.formData.fArr.length - 1 ? this.formData.fArr[i] : 1
                    fArr.push(f)
                    let min = i <= this.formData.boundTableData.length - 1 ? this.formData.boundTableData[i].min : 0
                    let max = i <= this.formData.boundTableData.length - 1 ? this.formData.boundTableData[i].max : 9999999
                    boundTableData.push({
                        var: 'x' + (i + 1),
                        min: min,
                        max: max,
                    })
                }
                this.formData.boundTableData = boundTableData
                this.formData.fArr = fArr

                this.adjustAArr()
                this.adjustBArr()
            },
            adjustAArr() {
                this.formData.aArr.forEach((row, rowIndex) => {
                    while(row.length < this.formData.varNum) {
                        row.push(1)
                    }

                    this.formData.aArr[rowIndex].length = this.formData.varNum
                })
            },
            adjustBArr() {
                while(this.formData.bArr.length < this.formData.aArr.length) {
                    this.formData.bArr.push(1)
                }
            },
        },
        computed: {
            haveChampion() {
                if (this.flatBoundDataTree) {
                    for (let i = 0; i < this.flatBoundDataTree.length; i++) {
                        let row = this.flatBoundDataTree[i]
                        if (this.isChampion(row)) {
                            return true
                        }
                    }
                }
                return false
            }
        },
        mounted () {
            if (window.location.href.indexOf("https://") >= 0) {
                window.location = window.location.href.replace('https://', 'http://')
            }

            let linprog1FormData = this.$localStorage.get('linprog1FormData')
            // console.log('linprog1FormData:', linprog1FormData)
            if(linprog1FormData) {
                let backupFormData = lodash.clone(this.formData)
                try {
                    this.formData = JSON.parse(linprog1FormData)
                } catch (e) {
                    this.formData = lodash.clone(backupFormData)
                    this.genBoundTableData()
                }
            } else {
                this.genBoundTableData()
            }

        },
    }
</script>

<style scoped>
    .champion_cls {
        color: red;
        font-size: 22px;
    }
</style>
